Jump game V [Sliding Window, Mono Stack, Segment Tree]

Time: O(N); Space: O(N); hard

Given an array of integers arr and an integer d. In one step you can jump from index i to index:

  • i + x where: i + x < arr.length and 0 < x <= d.

  • i - x where: i - x >= 0 and 0 < x <= d. In addition, you can only jump from index i to index j if arr[i] > arr[j] and arr[i] > arr[k] for all indices k between i and j (More formally min(i, j) < k < max(i, j)).

You can choose any index of the array and start jumping. Return the maximum number of indices you can visit.

Notice that you can not jump outside of the array at any time.

Example 1:

Input: arr = [6,4,14,6,8,13,9,7,10,6,12], d = 2

Output: 4

Explanation:

  • You can start at index 10. You can jump 10 –> 8 –> 6 –> 7 as shown.

  • Note that if you start at index 6 you can only jump to index 7.

  • You cannot jump to index 5 because 13 > 9.

  • You cannot jump to index 4 because index 5 is between index 4 and 6 and 13 > 9.

  • Similarly You cannot jump from index 3 to index 2 or index 1.

Example 2:

Input: arr = [3,3,3,3,3], d = 3

Output: 1

Explanation:

  • You can start at any index. You always cannot jump to any index.

Example 3:

Input: arr = [7,6,5,4,3,2,1], d = 1

Output: 7

Explanation:

  • Start at index 0. You can visit all the indicies.

Example 4:

Input: arr = [7,1,7,1,7,1], d = 2

Output: 2

Example 5:

Input: arr = [66], d = 1

Output: 1

Constraints:

  • 1 <= len(arr) <= 1000

  • 1 <= arr[i] <= 10^5

  • 1 <= d <= len(arr)

Hints:

  1. Use dynamic programming. dp[i] is max jumps you can do starting from index i. Answer is max(dp[i]).

  2. dp[i] = 1 + max (dp[j]) where j is all indices you can reach from i.

1. Dynamic programming (sliding window + top-down dp) [O(N), O(N)]

[5]:
import collections
import itertools

class Solution1(object):
    """
    Time: O(N)
    Space: O(N)
    """
    def maxJumps(self, arr, d):
        """
        :type arr: List[int]
        :type d: int
        :rtype: int
        """
        def dp(arr, d, i, left, right, lookup):
            if lookup[i]:
                return lookup[i]
            lookup[i] = 1
            for j in itertools.chain(left[i], right[i]):
                # each dp[j] will be visited at most twice
                lookup[i] = max(lookup[i], dp(arr, d, j, left, right, lookup)+1)
            return lookup[i]

        left, decreasing_dq = [[] for _ in range(len(arr))], collections.deque()

        for i in range(len(arr)):
            if decreasing_dq and i - decreasing_dq[0] == d+1:
                decreasing_dq.popleft()
            while decreasing_dq and arr[decreasing_dq[-1]] < arr[i]:
                if left[i] and arr[left[i][-1]] != arr[decreasing_dq[-1]]:
                    left[i] = []
                left[i].append(decreasing_dq.pop())
            decreasing_dq.append(i)

        right, decreasing_dq = [[] for _ in range(len(arr))], collections.deque()

        for i in reversed(range(len(arr))):
            if decreasing_dq and decreasing_dq[0] - i == d+1:
                decreasing_dq.popleft()
            while decreasing_dq and arr[decreasing_dq[-1]] < arr[i]:
                if right[i] and arr[right[i][-1]] != arr[decreasing_dq[-1]]:
                    right[i] = []
                right[i].append(decreasing_dq.pop())
            decreasing_dq.append(i)

        lookup = [0]*len(arr)

        return max(map(lambda x: dp(arr, d, x, left, right, lookup), range(len(arr))))
[6]:
s = Solution1()

arr = [6,4,14,6,8,13,9,7,10,6,12]
d = 2
assert s.maxJumps(arr, d) == 4

arr = [3,3,3,3,3]
d = 3
assert s.maxJumps(arr, d) == 1

arr = [7,6,5,4,3,2,1]
d = 1
assert s.maxJumps(arr, d) == 7

arr = [7,1,7,1,7,1]
d = 2
assert s.maxJumps(arr, d) == 2

arr = [66]
d = 1
assert s.maxJumps(arr, d) == 1

2. Dynamic programming (mono stack + bottom-up dp) []

[7]:
import itertools

class Solution2(object):
    def maxJumps(self, arr, d):
        """
        :type arr: List[int]
        :type d: int
        :rtype: int
        """
        left, decreasing_stk = [[] for _ in range(len(arr))], []

        for i in range(len(arr)):
            while decreasing_stk and arr[decreasing_stk[-1]] < arr[i]:
                if i - decreasing_stk[-1] <= d:
                    if left[i] and arr[left[i][-1]] != arr[decreasing_stk[-1]]:
                        left[i] = []
                    left[i].append(decreasing_stk[-1])
                decreasing_stk.pop()
            decreasing_stk.append(i)

        right, decreasing_stk = [[] for _ in range(len(arr))], []

        for i in reversed(range(len(arr))):
            while decreasing_stk and arr[decreasing_stk[-1]] < arr[i]:
                if decreasing_stk[-1] - i <= d:
                    if right[i] and arr[right[i][-1]] != arr[decreasing_stk[-1]]:
                        right[i] = []
                    right[i].append(decreasing_stk[-1])
                decreasing_stk.pop()
            decreasing_stk.append(i)

        dp = [0]*len(arr)

        for a, i in sorted([a, i] for i, a in enumerate(arr)):
            dp[i] = 1
            for j in itertools.chain(left[i], right[i]):
                # each dp[j] will be visited at most twice
                dp[i] = max(dp[i], dp[j]+1)

        return max(dp)
[8]:
s = Solution2()

arr = [6,4,14,6,8,13,9,7,10,6,12]
d = 2
assert s.maxJumps(arr, d) == 4

arr = [3,3,3,3,3]
d = 3
assert s.maxJumps(arr, d) == 1

arr = [7,6,5,4,3,2,1]
d = 1
assert s.maxJumps(arr, d) == 7

arr = [7,1,7,1,7,1]
d = 2
assert s.maxJumps(arr, d) == 2

arr = [66]
d = 1
assert s.maxJumps(arr, d) == 1

3. Dynamic programming (mono stack + bottom-up dp + segment tree) []

[20]:
class SegmentTree(object):
    def __init__(self, N,
                 build_fn=lambda x, y: [y]*(2*x),
                 query_fn=max,
                 update_fn=lambda x, y: y if x is None else x+y,
                 default_val=0):
        self.N = N
        self.H = (N-1).bit_length()
        self.query_fn = query_fn
        self.update_fn = update_fn
        self.default_val = default_val
        self.tree = build_fn(N, default_val)
        self.lazy = [None]*N

    def __apply(self, x, val):
        self.tree[x] = self.update_fn(self.tree[x], val)
        if x < self.N:
            self.lazy[x] = self.update_fn(self.lazy[x], val)

    def update(self, L, R, h):  # Time: O(logN), Space: O(N)
        def pull(x):
            while x > 1:
                x //= 2
                self.tree[x] = self.query_fn(self.tree[x*2], self.tree[x*2+1])
                if self.lazy[x] is not None:
                    self.tree[x] = self.update_fn(self.tree[x], self.lazy[x])
        L += self.N
        R += self.N
        L0, R0 = L, R
        while L <= R:
            if L & 1:  # is right child
                self.__apply(L, h)
                L += 1
            if R & 1 == 0:  # is left child
                self.__apply(R, h)
                R -= 1
            L //= 2
            R //= 2
        pull(L0)
        pull(R0)

    def query(self, L, R):  # Time: O(logN), Space: O(N)
        def push(x):
            n = 2**self.H
            while n != 1:
                y = x // n
                if self.lazy[y] is not None:
                    self.__apply(y*2, self.lazy[y])
                    self.__apply(y*2 + 1, self.lazy[y])
                    self.lazy[y] = None
                n //= 2

        result = self.default_val
        if L > R:
            return result

        L += self.N
        R += self.N
        push(L)
        push(R)
        while L <= R:
            if L & 1:  # is right child
                result = self.query_fn(result, self.tree[L])
                L += 1
            if R & 1 == 0:  # is left child
                result = self.query_fn(result, self.tree[R])
                R -= 1
            L //= 2
            R //= 2
        return result

    def __str__(self):
        showList = []
        for i in xrange(self.N):
            showList.append(self.query(i, i))
        return ",".join(map(str, showList))
[21]:
class Solution3(object):
    def maxJumps(self, arr, d):
        """
        :type arr: List[int]
        :type d: int
        :rtype: int
        """
        left, decreasing_stk = [x for x in range(len(arr))], []

        for i in range(len(arr)):
            while decreasing_stk and arr[decreasing_stk[-1]] < arr[i]:
                if i - decreasing_stk[-1] <= d:
                    left[i] = decreasing_stk[-1]
                decreasing_stk.pop()
            decreasing_stk.append(i)

        right, decreasing_stk = [x for x in range(len(arr))], []

        for i in reversed(range(len(arr))):
            while decreasing_stk and arr[decreasing_stk[-1]] < arr[i]:
                if decreasing_stk[-1] - i <= d:
                    right[i] = decreasing_stk[-1]
                decreasing_stk.pop()
            decreasing_stk.append(i)

        segment_tree = SegmentTree(len(arr))

        for _, i in sorted([x, i] for i, x in enumerate(arr)):
            segment_tree.update(i, i, segment_tree.query(left[i], right[i]) + 1)

        return segment_tree.query(0, len(arr)-1)
[22]:
s = Solution3()

arr = [6,4,14,6,8,13,9,7,10,6,12]
d = 2
assert s.maxJumps(arr, d) == 4

arr = [3,3,3,3,3]
d = 3
assert s.maxJumps(arr, d) == 1

arr = [7,6,5,4,3,2,1]
d = 1
assert s.maxJumps(arr, d) == 7

arr = [7,1,7,1,7,1]
d = 2
assert s.maxJumps(arr, d) == 2

arr = [66]
d = 1
assert s.maxJumps(arr, d) == 1